package com.thunisoft.converter.poi;

import org.apache.poi.hwpf.HWPFDocumentCore;
import org.apache.poi.hwpf.usermodel.CharacterRun;
import org.apache.poi.hwpf.usermodel.LineSpacingDescriptor;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Section;

import com.thunisoft.converter.ConverterUtils;

import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.JFileChooser;
import javax.swing.JScrollPane;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.UIManager;

import java.awt.Component;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class DocFileDebugger extends javax.swing.JFrame {

    private static final long serialVersionUID = -6494118594681370384L;

    private File currentDir = new File(".");
    private String currentSelected = null;
    private String currentFileName = null;
    private HWPFDocumentCore currentDocument = null;

    private javax.swing.JMenuItem aboutMenuItem;
    private javax.swing.JMenuItem contentsMenuItem;
    private javax.swing.JMenuItem copyMenuItem;
    private javax.swing.JMenuItem cutMenuItem;
    private javax.swing.JMenuItem deleteMenuItem;
    private javax.swing.JMenu editMenu;
    private javax.swing.JMenuItem exitMenuItem;
    private javax.swing.JMenu fileMenu;
    private javax.swing.JMenu helpMenu;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JSplitPane jSplitPane1;
    private javax.swing.JTextPane jTextPane1;
    private javax.swing.JTree jTree1;
    private javax.swing.JMenuBar menuBar;
    private javax.swing.JMenuItem openMenuItem;
    private javax.swing.JMenuItem pasteMenuItem;
    private javax.swing.JMenuItem saveAsMenuItem;
    private javax.swing.JMenuItem saveMenuItem;
    private JPanel documentPanel = new JPanel();

    // End of variables declaration//GEN-END:variables

    /**
     * Constructor.
     */
    public DocFileDebugger() {
        initComponents();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    private void initComponents() {
        jSplitPane1 = new javax.swing.JSplitPane();
        jScrollPane1 = new javax.swing.JScrollPane();
        jTree1 = new javax.swing.JTree();
        jScrollPane2 = new javax.swing.JScrollPane();
        jTextPane1 = new javax.swing.JTextPane();
        menuBar = new javax.swing.JMenuBar();
        fileMenu = new javax.swing.JMenu();
        openMenuItem = new javax.swing.JMenuItem();
        saveMenuItem = new javax.swing.JMenuItem();
        saveAsMenuItem = new javax.swing.JMenuItem();
        exitMenuItem = new javax.swing.JMenuItem();
        editMenu = new javax.swing.JMenu();
        cutMenuItem = new javax.swing.JMenuItem();
        copyMenuItem = new javax.swing.JMenuItem();
        pasteMenuItem = new javax.swing.JMenuItem();
        deleteMenuItem = new javax.swing.JMenuItem();
        helpMenu = new javax.swing.JMenu();
        contentsMenuItem = new javax.swing.JMenuItem();
        aboutMenuItem = new javax.swing.JMenuItem();

        jTree1.setCellRenderer(new DocTreeCellRenderer());
        jTree1.setModel(null);

        setTitle("True Type File Viewer");
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                exitForm(evt);
            }
        });

        jScrollPane1.setBorder(new javax.swing.border.BevelBorder(javax.swing.border.BevelBorder.RAISED));
        jScrollPane1.setPreferredSize(new java.awt.Dimension(300, 500));
        jTree1.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() {
            public void valueChanged(javax.swing.event.TreeSelectionEvent evt) {
                jTree1ValueChanged(evt);
            }
        });

        jScrollPane1.setViewportView(jTree1);

        jSplitPane1.setRightComponent(jScrollPane2);

        jScrollPane2.setPreferredSize(new java.awt.Dimension(300, 500));
        jScrollPane2.setViewportView(jTextPane1);

        jSplitPane1.setLeftComponent(jScrollPane1);

        JScrollPane documentScroller = new JScrollPane();
        // documentScroller.setPreferredSize( new Dimension( 300, 500 ) );
        documentScroller.setViewportView(documentPanel);

        getContentPane().add(jSplitPane1, java.awt.BorderLayout.CENTER);

        fileMenu.setText("File");
        openMenuItem.setText("Open");
        openMenuItem.setToolTipText("Open True Type file");
        openMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                openMenuItemActionPerformed(evt);
            }
        });

        fileMenu.add(openMenuItem);

        saveMenuItem.setText("Save");

        saveAsMenuItem.setText("Save As ...");

        exitMenuItem.setText("Exit");
        exitMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                exitMenuItemActionPerformed(evt);
            }
        });

        fileMenu.add(exitMenuItem);

        menuBar.add(fileMenu);

        editMenu.setText("Edit");
        cutMenuItem.setText("Cut");
        editMenu.add(cutMenuItem);

        copyMenuItem.setText("Copy");
        editMenu.add(copyMenuItem);

        pasteMenuItem.setText("Paste");
        editMenu.add(pasteMenuItem);

        deleteMenuItem.setText("Delete");
        editMenu.add(deleteMenuItem);

        helpMenu.setText("Help");
        contentsMenuItem.setText("Contents");
        helpMenu.add(contentsMenuItem);

        aboutMenuItem.setText("About");
        helpMenu.add(aboutMenuItem);

        setJMenuBar(menuBar);

        java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
        setBounds((screenSize.width - 700) / 2, (screenSize.height - 600) / 2, 700, 600);
    }// GEN-END:initComponents

    private void openMenuItemActionPerformed(java.awt.event.ActionEvent evt) {
        JFileChooser chooser = new JFileChooser();
        chooser.setCurrentDirectory(currentDir);

        int result = chooser.showOpenDialog(DocFileDebugger.this);
        if (result == JFileChooser.APPROVE_OPTION) {
            String name = chooser.getSelectedFile().getPath();
            currentDir = new File(name).getParentFile();
            try {
                readDocFile(name, "");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void jTree1ValueChanged(javax.swing.event.TreeSelectionEvent evt) {
        TreePath path = jTree1.getSelectionPath();
        if (path != null) {
            try {
                Object selectedNode = path.getLastPathComponent();
                String data = convertToString(selectedNode);

                if (data != null) {
                    jTextPane1.setText(data);
                } else {
                    jTextPane1.setText("");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }// GEN-LAST:event_jTree1ValueChanged

    private String convertToString(Object selectedNode) throws Exception {
        String data = null;
        if (selectedNode instanceof DomainRoot) {
            data = this.currentFileName;
        } else if (selectedNode instanceof DomainSection) {

        } else if (selectedNode instanceof DomainParagraph) {
            DomainParagraph paragraph = (DomainParagraph) selectedNode;
            data = this.parserParagraph(paragraph.paragraph);
        } else if (selectedNode instanceof DomainCharacterRun) {
            DomainCharacterRun charRun = (DomainCharacterRun) selectedNode;
            data = this.parserCharacterRun(charRun.characterRun);
        }
        return data;
    }

    private String parserParagraph(Paragraph paragraph) {
        List<Object[]> messages = new ArrayList<Object[]>();

        messages.add(new Object[] { "StyleIndex", paragraph.getStyleIndex() });
        messages.add(new Object[] { "InTable", paragraph.isInTable() });
        messages.add(new Object[] { "InList", paragraph.isInList() });
        messages.add(new Object[] { "Ilvl", paragraph.getIlvl() });
        messages.add(new Object[] { "Justification", paragraph.getJustification() });
        messages.add(new Object[] { "keepOnPage", paragraph.keepOnPage() });
        messages.add(new Object[] { "keepWithNext", paragraph.keepWithNext() });
        messages.add(new Object[] { "pageBreakBefore", paragraph.pageBreakBefore() });
        messages.add(new Object[] { "LineNotNumbered", paragraph.isLineNotNumbered() });
        messages.add(new Object[] { "SideBySide", paragraph.isSideBySide() });
        messages.add(new Object[] { "AutoHyphenated", paragraph.isAutoHyphenated() });
        messages.add(new Object[] { "IndentFromRight", paragraph.getIndentFromRight() });
        messages.add(new Object[] { "IndentFromLeft", paragraph.getIndentFromLeft() });
        messages.add(new Object[] { "FirstLineIndent", paragraph.getFirstLineIndent() });
        LineSpacingDescriptor desc = paragraph.getLineSpacing();
        Short dya = (Short) ConverterUtils.getField(LineSpacingDescriptor.class, desc, "_dyaLine");
        int dyaInt = 0x0000FFFF & dya.intValue();
        Short mul = (Short) ConverterUtils.getField(LineSpacingDescriptor.class, desc, "_fMultiLinespace");
        messages.add(new Object[] { "LineSpacing", "dya=" + dyaInt + "  mul=" + mul });
        messages.add(new Object[] { "SpacingBefore", paragraph.getSpacingBefore() });
        messages.add(new Object[] { "SpacingAfter", paragraph.getSpacingAfter() });
        messages.add(new Object[] { "Kinsoku", paragraph.isKinsoku() });
        messages.add(new Object[] { "WordWrapped", paragraph.isWordWrapped() });
        messages.add(new Object[] { "FontAlignment", paragraph.getFontAlignment() });
        messages.add(new Object[] { "Vertical", paragraph.isVertical() });
        messages.add(new Object[] { "Backward", paragraph.isBackward() });
        messages.add(new Object[] { "TopBorder", paragraph.getTopBorder() });
        messages.add(new Object[] { "LeftBorder", paragraph.getLeftBorder() });
        messages.add(new Object[] { "BottomBorder", paragraph.getBottomBorder() });
        messages.add(new Object[] { "RightBorder", paragraph.getRightBorder() });

        return this.formatShowMessage(messages, 20);
    }

    private String parserCharacterRun(CharacterRun charRun) {
        List<Object[]> messages = new ArrayList<Object[]>();

        messages.add(new Object[] { "isBold", charRun.isBold() });
        messages.add(new Object[] { "fontName", charRun.getFontName() });
        messages.add(new Object[] { "CharacterSpacing", charRun.getCharacterSpacing() });
        messages.add(new Object[] { "VerticalOffset", charRun.getVerticalOffset() });
        messages.add(new Object[] { "Kerning", charRun.getKerning() });
        messages.add(new Object[] { charRun.text() });

        return this.formatShowMessage(messages, 20);
    }

    private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt) {
        this.clearCurrentFile();
        System.exit(0);
    }

    /**
     * Exit the Application.
     */
    private void exitForm(java.awt.event.WindowEvent evt) {
        this.clearCurrentFile();
        System.exit(0);
    }

    /**
     * @param args
     *            the command line arguments
     * 
     * @throws Exception
     *             If anything goes wrong.
     */
    public static void main(String[] args) throws Exception {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        DocFileDebugger viewer = new DocFileDebugger();
        String filename = null;
        String password = "";
        for (int i = 0; i < args.length; i++) {
            filename = args[i];
        }

        if (filename != null) {
            viewer.readDocFile(filename, password);
        }
        viewer.setVisible(true);
    }

    private void readDocFile(String file, String password) throws Exception {
        this.clearCurrentFile();

        File selectedFile = new File(file);
        String path = selectedFile.getAbsolutePath();

        this.currentSelected = path;
        this.currentFileName = selectedFile.getName();
        this.currentDocument = WordToFoUtils.loadDoc(selectedFile);

        TreeModel model = new DocTreeModel();
        jTree1.setModel(model);
        setTitle("Doc Viewer - " + path);
    }

    private void clearCurrentFile() {
        if (this.currentSelected != null) {
            this.currentSelected = null;
            this.currentFileName = null;
            this.currentDocument = null;
        }
    }

    private String formatShowMessage(List<Object[]> messages, int keyLength) {
        StringBuilder sb = new StringBuilder();
        for (Object[] pair : messages) {
            String key = (String) pair[0];
            sb.append(key);
            if (pair.length > 1) {
                int spaceLength = keyLength - key.length();
                if (spaceLength > 0) {
                    for (int i = 0; i < spaceLength; i++) {
                        sb.append(" ");
                    }
                } else {
                    sb.append(" ");
                }
                sb.append(pair[1]);
            }
            sb.append("\n");
        }
        return sb.toString();
    }

    /**
     * This will print out a message telling how to use this utility.
     */
    private static void usage() {
        System.err.println("usage: java -jar pdfbox-app-x.y.z.jar PDFDebugger [OPTIONS] <input-file>\n"
                + "  -password <password>      Password to decrypt the document\n"
                + "  -nonSeq                   Enables the new non-sequential parser\n"
                + "  <input-file>              The PDF document to be loaded\n");
    }

    private class DomainRoot {

        private Range docRange;

        private List<DomainSection> sectionList = new ArrayList<DomainSection>();

        public DomainRoot(HWPFDocumentCore document) {
            this.docRange = document.getRange();
            int sectionNum = this.docRange.numSections();
            for (int i = 0; i < sectionNum; i++) {
                Section section = this.docRange.getSection(i);
                this.sectionList.add(new DomainSection(i, section));
            }
        }
    }

    private class DomainSection {

        private int index;

        private Section section;

        private List<DomainParagraph> paragraphList = new ArrayList<DomainParagraph>();

        public DomainSection(int index, Section section) {
            this.index = index;
            this.section = section;
            int paragraphNum = this.section.numParagraphs();
            for (int i = 0; i < paragraphNum; i++) {
                Paragraph paragraph = this.section.getParagraph(i);
                this.paragraphList.add(new DomainParagraph(i, paragraph));
            }
        }
    }

    private class DomainParagraph {

        private int index;

        private Paragraph paragraph;

        private List<DomainCharacterRun> characterRunList = new ArrayList<DomainCharacterRun>();

        public DomainParagraph(int index, Paragraph paragraph) {
            this.index = index;
            this.paragraph = paragraph;
            int characterNum = this.paragraph.numCharacterRuns();
            for (int i = 0; i < characterNum; i++) {
                CharacterRun characterRun = this.paragraph.getCharacterRun(i);
                this.characterRunList.add(new DomainCharacterRun(i, characterRun));
            }
        }
    }

    private class DomainCharacterRun {

        private int index;

        private CharacterRun characterRun;

        public DomainCharacterRun(int index, CharacterRun characterRun) {
            this.index = index;
            this.characterRun = characterRun;
        }
    }

    private class DocTreeModel implements TreeModel {

        /**
         * constructor.
         */
        public DocTreeModel() {
            // default constructor
        }

        /**
         * Adds a listener for the <code>TreeModelEvent</code> posted after the
         * tree changes.
         * 
         * @param l
         *            the listener to add
         * @see #removeTreeModelListener
         * 
         */
        public void addTreeModelListener(TreeModelListener l) {
            // required for interface
        }

        /**
         * Returns the child of <code>parent</code> at index <code>index</code>
         * in the parent's child array. <code>parent</code> must be a node
         * previously obtained from this data source. This should not return
         * <code>null</code> if <code>index</code> is a valid index for
         * <code>parent</code> (that is <code>index >= 0 &&
         * index < getChildCount(parent</code>)).
         * 
         * @param parent
         *            a node in the tree, obtained from this data source
         * @param index
         *            The index into the parent object to location the child
         *            object.
         * @return the child of <code>parent</code> at index <code>index</code>
         * 
         */
        public Object getChild(Object parent, int index) {
            if (parent instanceof DomainRoot) {
                return ((DomainRoot) parent).sectionList.get(index);
            } else if (parent instanceof DomainSection) {
                return ((DomainSection) parent).paragraphList.get(index);
            } else if (parent instanceof DomainParagraph) {
                return ((DomainParagraph) parent).characterRunList.get(index);
            }
            throw new RuntimeException("Unknown COS type " + parent.getClass().getName());
        }

        /**
         * Returns the number of children of <code>parent</code>. Returns 0 if
         * the node is a leaf or if it has no children. <code>parent</code> must
         * be a node previously obtained from this data source.
         * 
         * @param parent
         *            a node in the tree, obtained from this data source
         * @return the number of children of the node <code>parent</code>
         * 
         */
        public int getChildCount(Object parent) {
            if (parent instanceof DomainRoot) {
                return ((DomainRoot) parent).sectionList.size();
            } else if (parent instanceof DomainSection) {
                return ((DomainSection) parent).paragraphList.size();
            } else if (parent instanceof DomainParagraph) {
                return ((DomainParagraph) parent).characterRunList.size();
            }
            throw new RuntimeException("Unknown COS type " + parent.getClass().getName());
        }

        /**
         * Returns the index of child in parent. If <code>parent</code> is
         * <code>null</code> or <code>child</code> is <code>null</code>, returns
         * -1.
         * 
         * @param parent
         *            a note in the tree, obtained from this data source
         * @param child
         *            the node we are interested in
         * @return the index of the child in the parent, or -1 if either
         *         <code>child</code> or <code>parent</code> are
         *         <code>null</code>
         * 
         */
        public int getIndexOfChild(Object parent, Object child) {
            if (parent != null && child != null) {
                if (parent instanceof DomainRoot) {
                    return ((DomainRoot) parent).sectionList.indexOf(child);
                } else if (parent instanceof DomainSection) {
                    return ((DomainSection) parent).paragraphList.indexOf(child);
                } else if (parent instanceof DomainParagraph) {
                    return ((DomainParagraph) parent).characterRunList.indexOf(child);
                }
            }
            return -1;
        }

        /**
         * Returns the root of the tree. Returns <code>null</code> only if the
         * tree has no nodes.
         * 
         * @return the root of the tree
         * 
         */
        public Object getRoot() {
            return new DomainRoot(currentDocument);
        }

        /**
         * Returns <code>true</code> if <code>node</code> is a leaf. It is
         * possible for this method to return <code>false</code> even if
         * <code>node</code> has no children. A directory in a filesystem, for
         * example, may contain no files; the node representing the directory is
         * not a leaf, but it also has no children.
         * 
         * @param node
         *            a node in the tree, obtained from this data source
         * @return true if <code>node</code> is a leaf
         * 
         */
        public boolean isLeaf(Object node) {
            boolean isLeaf = true;
            if (node instanceof DomainRoot) {
                isLeaf = false;
            } else if (node instanceof DomainSection) {
                isLeaf = false;
            } else if (node instanceof DomainParagraph) {
                isLeaf = false;
            }
            return isLeaf;
        }

        /**
         * Removes a listener previously added with
         * <code>addTreeModelListener</code>.
         * 
         * @see #addTreeModelListener
         * @param l
         *            the listener to remove
         * 
         */

        public void removeTreeModelListener(TreeModelListener l) {
            // required for interface
        }

        /**
         * Messaged when the user has altered the value for the item identified
         * by <code>path</code> to <code>newValue</code>. If
         * <code>newValue</code> signifies a truly new value the model should
         * post a <code>treeNodesChanged</code> event.
         * 
         * @param path
         *            path to the node that the user has altered
         * @param newValue
         *            the new value from the TreeCellEditor
         * 
         */
        public void valueForPathChanged(TreePath path, Object newValue) {
            // required for interface
        }

    }

    private class DocTreeCellRenderer extends DefaultTreeCellRenderer {

        private static final long serialVersionUID = -7547234907057495753L;

        public Component getTreeCellRendererComponent(JTree tree, Object nodeValue, boolean isSelected,
                boolean expanded, boolean leaf, int row, boolean componentHasFocus) {
            nodeValue = convertToTreeObject(nodeValue);
            return super.getTreeCellRendererComponent(tree, nodeValue, isSelected, expanded, leaf, row,
                    componentHasFocus);
        }

        private Object convertToTreeObject(Object nodeValue) {
            if (nodeValue instanceof DomainRoot) {
                nodeValue = DocFileDebugger.this.currentFileName;
            } else if (nodeValue instanceof DomainSection) {
                DomainSection sec = (DomainSection) nodeValue;
                nodeValue = "section[" + sec.index + "]";
            } else if (nodeValue instanceof DomainParagraph) {
                DomainParagraph paragraph = (DomainParagraph) nodeValue;
                nodeValue = "paragraph[" + paragraph.index + "]";
            } else if (nodeValue instanceof DomainCharacterRun) {
                DomainCharacterRun characterRun = (DomainCharacterRun) nodeValue;
                nodeValue = "characterRun[" + characterRun.index + "]";
            }
            return nodeValue;
        }
    }
}
